/***************************************************************************
 *   Copyright (C) 2007 by Sindre Aam�s                                    *
 *   aamas@stud.ntnu.no                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License version 2 as     *
 *   published by the Free Software Foundation.                            *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License version 2 for more details.                *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   version 2 along with this program; if not, write to the               *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#ifndef MEMORY_H
#define MEMORY_H

struct saveState;

#include "int.h"
#include "video.h"
#include "sound.h"
#include "minkeeper.h"
#include "interrupter.h"
#include "rtc.h"
#include <string>

namespace Gambatte {
class InputGetter;
class FilterInfo;
}

class Memory {
public:
	enum { COUNTER_DISABLED = 0xFFFFFFFFu };
	
private:
	enum cartridgetype { plain, mbc1, mbc2, mbc3, mbc5 };
	enum events { END, BLIT, OAM, UNHALT, DMA, HDMA_RESCHEDULE, INTERRUPTS };
	enum { NUM_EVENTS = INTERRUPTS + 1 };
	enum irqEvents { /*MODE0, MODE1, MODE2, LYC,*/ TIMA, /*M0RESC,*/ SERIAL };
	
	unsigned char ioamhram[0x200];
	unsigned char vram[0x2000 * 2];
	unsigned char *rmem[0x10];
	unsigned char *wmem[0x10];
	
	unsigned char *memchunk;
	unsigned char *romdata[2];
	unsigned char *wramdata[2];
	unsigned char *rambankdata;
	unsigned char *rdisabled_ram;
	unsigned char *wdisabled_ram;
	unsigned char *oamDmaSrc;
	unsigned char *vrambank;
	unsigned char *rsrambankptr;
	unsigned char *wsrambankptr;
	
	Gambatte::InputGetter *getInput;

	unsigned long div_lastUpdate;
	unsigned long tima_lastUpdate;
	unsigned long next_timatime;
	unsigned long minIntTime;
	unsigned long next_irqEventTime;
	unsigned long tmatime;
	unsigned long next_serialtime;
	unsigned long lastOamDmaUpdate;
	unsigned long lastBlitTime;
	
	MinKeeper<NUM_EVENTS> eventTimes;
	LCD display;
	PSG sound;
	Interrupter interrupter;
	Rtc rtc;

	irqEvents next_irqEvent;
	cartridgetype romtype;
	
	std::string defaultSaveBasePath;
	std::string saveDir;
	
	unsigned short rombanks;
	unsigned short rombank;
	unsigned short dmaSource;
	unsigned short dmaDestination;
	
	unsigned char rambank;
	unsigned char rambanks;
	unsigned char oamDmaArea1Lower;
	unsigned char oamDmaArea1Width;
	unsigned char oamDmaArea2Upper;
	unsigned char oamDmaPos;

	bool cgb;
	bool doubleSpeed;
	bool IME;
	bool enable_ram;
	bool rambank_mode;
	bool battery, rtcRom;
	bool hdma_transfer;
	bool active;
	bool blanklcd;

	void updateInput();
	void decEventCycles(int eventId, unsigned long dec);

	void setRombank();
	void setRambank();
	void setBanks();
	void oamDmaInitSetup();
	void setOamDmaArea();
	void updateOamDma(unsigned long cycleCounter);
	void startOamDma(unsigned long cycleCounter);
	void endOamDma(unsigned long cycleCounter);
	void setOamDmaSrc();
	
	unsigned nontrivial_ff_read(unsigned P, unsigned long cycleCounter);
	unsigned nontrivial_read(unsigned P, unsigned long cycleCounter);
	void nontrivial_ff_write(unsigned P, unsigned data, unsigned long cycleCounter);
	void mbc_write(unsigned P, unsigned data);
	void nontrivial_write(unsigned P, unsigned data, unsigned long cycleCounter);

	void set_irqEvent();
	void update_irqEvents(unsigned long cc);
	void update_tima(unsigned long cycleCounter);
	
	void rescheduleIrq(unsigned long cycleCounter);
	void rescheduleHdmaReschedule();
	
	bool isDoubleSpeed() const { return doubleSpeed; }

public:
	Memory(const Interrupter &interrupter);
	~Memory();

	void setStatePtrs(SaveState &state);
	unsigned long saveState(SaveState &state, unsigned long cc);
	void loadState(const SaveState &state/*, unsigned long oldCc*/);
	void loadSavedata();
	void saveSavedata();
	const std::string saveBasePath() const;
	
	void setOsdElement(std::auto_ptr<OsdElement> osdElement) {
		display.setOsdElement(osdElement);
	}

	void speedChange(unsigned long cycleCounter);
	bool isCgb() const { return cgb; }
	bool getIME() const { return IME; }
	unsigned long getNextEventTime() const { return eventTimes.minValue(); }
	
	bool isActive() const { return active; }
	
	int cyclesSinceBlit(const unsigned long cc) const {
		return lastBlitTime == COUNTER_DISABLED ? -1 : static_cast<int>((cc - lastBlitTime) >> isDoubleSpeed());
	}

	void ei(unsigned long cycleCounter);

	void di() {
		IME = 0;
		eventTimes.setValue<INTERRUPTS>(COUNTER_DISABLED);
		
// 		next_eitime=0;
// 		if(next_event==EI) set_event();
	}

	unsigned ff_read(const unsigned P, const unsigned long cycleCounter) {
		return P < 0xFF80 ? nontrivial_ff_read(P, cycleCounter) : ioamhram[P - 0xFE00];
	}

	unsigned read(const unsigned P, const unsigned long cycleCounter) {
		return rmem[P >> 12] ? rmem[P >> 12][P] : nontrivial_read(P, cycleCounter);
	}

	void write(const unsigned P, const unsigned data, const unsigned long cycleCounter) {
		if (wmem[P >> 12])
			wmem[P >> 12][P] = data;
		else
			nontrivial_write(P, data, cycleCounter);
	}
	
	void ff_write(const unsigned P, const unsigned data, const unsigned long cycleCounter) {
		if (((P + 1) & 0xFF) < 0x81)
			nontrivial_ff_write(P, data, cycleCounter);
		else
			ioamhram[P - 0xFE00] = data;
	}

	unsigned long event(unsigned long cycleCounter);
	unsigned long resetCounters(unsigned long cycleCounter);

	bool loadROM(const char* romfile, bool forceDmg);
	void setSaveDir(const char *dir);

	void setInputGetter(Gambatte::InputGetter *getInput) {
		this->getInput = getInput;
	}

	void schedule_unhalt();
// 	void incEndtime(unsigned long inc);
	void setEndtime(unsigned long cc, unsigned long inc);
	
	void setSoundBuffer(Gambatte::uint_least32_t *const buf) { sound.setBuffer(buf); }
	unsigned fillSoundBuffer(unsigned long cc);
	
	void setVideoBuffer(Gambatte::uint_least32_t *const videoBuf, const unsigned pitch) {
		display.setVideoBuffer(videoBuf, pitch);
	}
	
	void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32);
};

#endif
